/*
 * Copyright 2017 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "src/sksl/SkSLCompiler.h"
#include "src/sksl/SkSLStringStream.h"

#include "tests/Test.h"

static void test(skiatest::Reporter* r, const char* src, const GrShaderCaps& caps,
                 std::vector<const char*> expectedH, std::vector<const char*> expectedCPP) {
    SkSL::Program::Settings settings;
    settings.fCaps = &caps;
    SkSL::Compiler compiler;
    SkSL::StringStream output;
    std::unique_ptr<SkSL::Program> program = compiler.convertProgram(
                                                             SkSL::Program::kFragmentProcessor_Kind,
                                                             SkSL::String(src),
                                                             settings);
    if (!program) {
        SkDebugf("Unexpected error compiling %s\n%s", src, compiler.errorText().c_str());
        return;
    }
    REPORTER_ASSERT(r, program);
    bool success = compiler.toH(*program, "Test", output);
    if (!success) {
        SkDebugf("Unexpected error compiling %s\n%s", src, compiler.errorText().c_str());
    }
    REPORTER_ASSERT(r, success);
    if (success) {
        for (const char* expected : expectedH) {
            bool found = strstr(output.str().c_str(), expected);
            if (!found) {
                SkDebugf("HEADER MISMATCH:\nsource:\n%s\n\nexpected:\n'%s'\n\nreceived:\n'%s'", src,
                         expected, output.str().c_str());
            }
            REPORTER_ASSERT(r, found);
        }
    }
    output.reset();
    success = compiler.toCPP(*program, "Test", output);
    if (!success) {
        SkDebugf("Unexpected error compiling %s\n%s", src, compiler.errorText().c_str());
    }
    REPORTER_ASSERT(r, success);
    if (success) {
        for (const char* expected : expectedCPP) {
            bool found = strstr(output.str().c_str(), expected);
            if (!found) {
                SkDebugf("CPP MISMATCH:\nsource:\n%s\n\nexpected:\n'%s'\n\nreceived:\n'%s'", src,
                         expected, output.str().c_str());
            }
            REPORTER_ASSERT(r, found);
        }
    }
}

static void test_failure(skiatest::Reporter* r, const char* src, const char* error) {
    SkSL::Compiler compiler;
    SkSL::Program::Settings settings;
    sk_sp<GrShaderCaps> caps = SkSL::ShaderCapsFactory::Default();
    settings.fCaps = caps.get();
    std::unique_ptr<SkSL::Program> program = compiler.convertProgram(
                                                             SkSL::Program::kFragmentProcessor_Kind,
                                                             SkSL::String(src),
                                                             settings);
    if (!compiler.errorCount()) {
        compiler.optimize(*program);
    }
    SkSL::String skError(error);
    if (compiler.errorText() != skError) {
        SkDebugf("SKSL ERROR:\n    source: %s\n    expected: %s    received: %s", src, error,
                 compiler.errorText().c_str());
    }
    REPORTER_ASSERT(r, compiler.errorText() == skError);
}

DEF_TEST(SkSLFPHelloWorld, r) {
    test(r,
         "/* HEADER */"
         "void main() {"
         "sk_OutColor = half4(1);"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {
             "/* HEADER */\n"
             "\n"
             "/**************************************************************************************************\n"
             " *** This file was autogenerated from GrTest.fp; do not modify.\n"
             " **************************************************************************************************/\n"
             "#ifndef GrTest_DEFINED\n"
             "#define GrTest_DEFINED\n"
             "#include \"include/core/SkTypes.h\"\n\n"
             "#include \"src/gpu/GrCoordTransform.h\"\n"
             "#include \"src/gpu/GrFragmentProcessor.h\"\n"
             "class GrTest : public GrFragmentProcessor {\n"
             "public:\n"
             "    static std::unique_ptr<GrFragmentProcessor> Make() {\n"
             "        return std::unique_ptr<GrFragmentProcessor>(new GrTest());\n"
             "    }\n"
             "    GrTest(const GrTest& src);\n"
             "    std::unique_ptr<GrFragmentProcessor> clone() const override;\n"
             "    const char* name() const override { return \"Test\"; }\n"
             "private:\n"
             "    GrTest()\n"
             "    : INHERITED(kGrTest_ClassID, kNone_OptimizationFlags) {\n"
             "    }\n"
             "    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;\n"
             "    void onGetGLSLProcessorKey(const GrShaderCaps&,GrProcessorKeyBuilder*) const "
                 "override;\n"
             "    bool onIsEqual(const GrFragmentProcessor&) const override;\n"
             "    GR_DECLARE_FRAGMENT_PROCESSOR_TEST\n"
             "    typedef GrFragmentProcessor INHERITED;\n"
             "};\n"
             "#endif\n"
         },
         {
             "/**************************************************************************************************\n"
             " *** This file was autogenerated from GrTest.fp; do not modify.\n"
             " **************************************************************************************************/\n"
             "#include \"GrTest.h\"\n\n"
             "#include \"include/gpu/GrTexture.h\"\n"
             "#include \"src/gpu/glsl/GrGLSLFragmentProcessor.h\"\n"
             "#include \"src/gpu/glsl/GrGLSLFragmentShaderBuilder.h\"\n"
             "#include \"src/gpu/glsl/GrGLSLProgramBuilder.h\"\n"
             "#include \"src/sksl/SkSLCPP.h\"\n"
             "#include \"src/sksl/SkSLUtil.h\"\n"
             "class GrGLSLTest : public GrGLSLFragmentProcessor {\n"
             "public:\n"
             "    GrGLSLTest() {}\n"
             "    void emitCode(EmitArgs& args) override {\n"
             "        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;\n"
             "        const GrTest& _outer = args.fFp.cast<GrTest>();\n"
             "        (void) _outer;\n"
             "        fragBuilder->codeAppendf(\"%s = half4(1.0);\\n\", args.fOutputColor);\n"
             "    }\n"
             "private:\n"
             "    void onSetData(const GrGLSLProgramDataManager& pdman, "
                                "const GrFragmentProcessor& _proc) override {\n"
             "    }\n"
             "};\n"
             "GrGLSLFragmentProcessor* GrTest::onCreateGLSLInstance() const {\n"
             "    return new GrGLSLTest();\n"
             "}\n"
             "void GrTest::onGetGLSLProcessorKey(const GrShaderCaps& caps, "
                                                "GrProcessorKeyBuilder* b) const {\n"
             "}\n"
             "bool GrTest::onIsEqual(const GrFragmentProcessor& other) const {\n"
             "    const GrTest& that = other.cast<GrTest>();\n"
             "    (void) that;\n"
             "    return true;\n"
             "}\n"
             "GrTest::GrTest(const GrTest& src)\n"
             ": INHERITED(kGrTest_ClassID, src.optimizationFlags()) {\n"
             "}\n"
             "std::unique_ptr<GrFragmentProcessor> GrTest::clone() const {\n"
             "    return std::unique_ptr<GrFragmentProcessor>(new GrTest(*this));\n"
             "}\n"
         });
}

DEF_TEST(SkSLFPInput, r) {
    test(r,
         "layout(key) in half2 point;"
         "void main() {"
         "sk_OutColor = half4(point, point);"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {
             "static std::unique_ptr<GrFragmentProcessor> Make(SkPoint point) {",
             "return std::unique_ptr<GrFragmentProcessor>(new GrTest(point));",
             "GrTest(SkPoint point)",
             ", point(point)"
         },
         {
             "fragBuilder->codeAppendf(\"%s = half4(half2(%f, %f), half2(%f, %f));\\n\", "
                                      "args.fOutputColor, _outer.point.fX, _outer.point.fY, "
                                      "_outer.point.fX, _outer.point.fY);",
             "if (point != that.point) return false;"
         });
}

DEF_TEST(SkSLFPUniform, r) {
    test(r,
         "uniform half4 color;"
         "void main() {"
         "sk_OutColor = color;"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {
             "static std::unique_ptr<GrFragmentProcessor> Make()"
         },
         {
            "colorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "
                                                        "\"color\");",
         });
}

// SkSLFPInUniform tests the simplest plumbing case, default type, no tracking
// with a setUniform template that supports inlining the value call with no
// local variable.
DEF_TEST(SkSLFPInUniform, r) {
    test(r,
         "in uniform half4 color;"
         "void main() {"
         "sk_OutColor = color;"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {
             "static std::unique_ptr<GrFragmentProcessor> Make(SkRect color) {",
         },
         {
            "colorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "
                                                        "\"color\");",
            "pdman.set4fv(colorVar, 1, reinterpret_cast<const float*>(&(_outer.color)));"
         });
}

// As above, but tests in uniform's ability to override the default ctype.
DEF_TEST(SkSLFPInUniformCType, r) {
    test(r,
         "layout(ctype=SkPMColor4f) in uniform half4 color;"
         "void main() {"
         "sk_OutColor = color;"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {
             "static std::unique_ptr<GrFragmentProcessor> Make(SkPMColor4f color) {",
         },
         {
            "colorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "
                                                        "\"color\");",
            "pdman.set4fv(colorVar, 1, (_outer.color).vec());"
         });
}

// Add state tracking to the default typed SkRect <-> half4 uniform. But since
// it now has to track state, the value inlining previously done for the
// setUniform call is removed in favor of a local variable.
DEF_TEST(SkSLFPTrackedInUniform, r) {
    test(r,
         "layout(tracked) in uniform half4 color;"
         "void main() {"
         "sk_OutColor = color;"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {
             "static std::unique_ptr<GrFragmentProcessor> Make(SkRect color) {",
         },
         {
            "SkRect colorPrev = SkRect::MakeEmpty();",
            "colorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "
                                                        "\"color\");",
            "const SkRect& colorValue = _outer.color;",
            "if (colorPrev.isEmpty() || colorPrev != colorValue) {",
            "colorPrev = colorValue;",
            "pdman.set4fv(colorVar, 1, reinterpret_cast<const float*>(&colorValue));"
         });
}

// Test the case where the template does not support variable inlining in
// setUniform (i.e. it references the value multiple times).
DEF_TEST(SkSLFPNonInlinedInUniform, r) {
    test(r,
         "in uniform half2 point;"
         "void main() {"
         "sk_OutColor = half4(point, point);"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {
             "static std::unique_ptr<GrFragmentProcessor> Make(SkPoint point) {",
         },
         {
            "pointVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType, "
                                                        "\"point\");",
            "const SkPoint& pointValue = _outer.point;",
            "pdman.set2f(pointVar, pointValue.fX, pointValue.fY);"
         });
}

// Test handling conditional uniforms (that use when= in layout), combined with
// state tracking and custom ctypes to really put the code generation through its paces.
DEF_TEST(SkSLFPConditionalInUniform, r) {
    test(r,
         "layout(key) in bool test;"
         "layout(ctype=SkPMColor4f, tracked, when=test) in uniform half4 color;"
         "void main() {"
         "  if (test) {"
         "    sk_OutColor = color;"
         "  } else {"
         "    sk_OutColor = half4(1);"
         "  }"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {
             "static std::unique_ptr<GrFragmentProcessor> Make(bool test, SkPMColor4f color) {",
         },
         {
            "SkPMColor4f colorPrev = {SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN}",
            "auto test = _outer.test;",
            "if (test) {",
            "colorVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "
                                                        "\"color\");",
            "if (colorVar.isValid()) {",
            "const SkPMColor4f& colorValue = _outer.color;",
            "if (colorPrev != colorValue) {",
            "colorPrev = colorValue;",
            "pdman.set4fv(colorVar, 1, colorValue.vec());"
         });
}

DEF_TEST(SkSLFPSections, r) {
    test(r,
         "@header { header section }"
         "void main() {"
         "sk_OutColor = half4(1);"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {
             "header section"
         },
         {});
    test(r,
         "@class { class section }"
         "void main() {"
         "sk_OutColor = half4(1);"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {
             "class GrTest : public GrFragmentProcessor {\n"
             "public:\n"
             " class section"
         },
         {});
    test(r,
         "@cpp { cpp section }"
         "void main() {"
         "sk_OutColor = half4(1);"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {},
         {"cpp section"});
    test(r,
         "@constructorParams { int x, float y, std::vector<float> z }"
         "in float w;"
         "void main() {"
         "sk_OutColor = half4(1);"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {
             "Make(float w,  int x, float y, std::vector<float> z )",
             "return std::unique_ptr<GrFragmentProcessor>(new GrTest(w, x, y, z));",
             "GrTest(float w,  int x, float y, std::vector<float> z )",
             ", w(w) {"
         },
         {});
    test(r,
         "@constructor { constructor section }"
         "void main() {"
         "sk_OutColor = half4(1);"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {
             "private:\n constructor section"
         },
         {});
    test(r,
         "@initializers { initializers section }"
         "void main() {"
         "sk_OutColor = half4(1);"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {
             ": INHERITED(kGrTest_ClassID, kNone_OptimizationFlags)\n    ,  initializers section"
         },
         {});
    test(r,
         "half x = 10;"
         "@emitCode { fragBuilder->codeAppendf(\"half y = %d\\n\", x * 2); }"
         "void main() {"
         "sk_OutColor = half4(1);"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {},
         {
            "x = 10.0;\n"
            " fragBuilder->codeAppendf(\"half y = %d\\n\", x * 2);"
         });
    test(r,
         "@fields { fields section }"
         "@clone { }"
         "void main() {"
         "sk_OutColor = half4(1);"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {
            "const char* name() const override { return \"Test\"; }\n"
            " fields section private:"
         },
         {});
    test(r,
         "@make { make section }"
         "void main() {"
         "sk_OutColor = half4(1);"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {
            "public:\n"
            " make section"
         },
         {});
    test(r,
         "uniform half calculated;"
         "layout(key) in half provided;"
         "@setData(varName) { varName.set1f(calculated, provided * 2); }"
         "void main() {"
         "sk_OutColor = half4(1);"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {},
         {
             "void onSetData(const GrGLSLProgramDataManager& varName, "
                            "const GrFragmentProcessor& _proc) override {\n",
             "UniformHandle& calculated = calculatedVar;",
             "auto provided = _outer.provided;",
             "varName.set1f(calculated, provided * 2);"
         });
    test(r,
         "@test(testDataName) { testDataName section }"
         "void main() {"
         "sk_OutColor = half4(1);"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {},
         {
             "#if GR_TEST_UTILS\n"
             "std::unique_ptr<GrFragmentProcessor> GrTest::TestCreate(GrProcessorTestData* testDataName) {\n"
             " testDataName section }\n"
             "#endif"
         });
}

DEF_TEST(SkSLFPTransformedCoords, r) {
    test(r,
         "void main() {"
         "sk_OutColor = half4(sk_TransformedCoords2D[0], sk_TransformedCoords2D[0]);"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {},
         {
            "SkString sk_TransformedCoords2D_0 = "
                                         "fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);",
            "fragBuilder->codeAppendf(\"%s = half4(%s, %s);\\n\", args.fOutputColor, "
                              "sk_TransformedCoords2D_0.c_str(), sk_TransformedCoords2D_0.c_str());"
         });

}

DEF_TEST(SkSLFPLayoutWhen, r) {
    test(r,
         "layout(when=someExpression(someOtherExpression())) uniform half sometimes;"
         "void main() {"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {},
         {
            "if (someExpression(someOtherExpression())) {\n"
            "            sometimesVar = args.fUniformHandler->addUniform"
         });

}

DEF_TEST(SkSLFPChildProcessors, r) {
    test(r,
         "in fragmentProcessor child1;"
         "in fragmentProcessor child2;"
         "void main() {"
         "    sk_OutColor = sample(child1) * sample(child2);"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {
            "this->registerChildProcessor(std::move(child1));",
            "this->registerChildProcessor(std::move(child2));"
         },
         {
            "SkString _sample93(\"_sample93\");\n",
            "this->invokeChild(_outer.child1_index, &_sample93, args);\n",
            "SkString _sample110(\"_sample110\");\n",
            "this->invokeChild(_outer.child2_index, &_sample110, args);\n",
            "fragBuilder->codeAppendf(\"%s = %s * %s;\\n\", args.fOutputColor, _sample93.c_str(), "
                                     "_sample110.c_str());\n",
            "this->registerChildProcessor(src.childProcessor(child1_index).clone());",
            "this->registerChildProcessor(src.childProcessor(child2_index).clone());"
         });
}

DEF_TEST(SkSLFPChildProcessorsWithInput, r) {
    test(r,
         "in fragmentProcessor child1;"
         "in fragmentProcessor child2;"
         "void main() {"
         "    half4 childIn = sk_InColor;"
         "    half4 childOut1 = sample(child1, childIn);"
         "    half4 childOut2 = sample(child2, childOut1);"
         "    sk_OutColor = childOut2;"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {
            "this->registerChildProcessor(std::move(child1));",
            "this->registerChildProcessor(std::move(child2));"
         },
         {
            "SkString _input0(\"childIn\");",
            "SkString _sample128(\"_sample128\");",
            "this->invokeChild(_outer.child1_index, _input0.c_str(), &_sample128, args);",
            "fragBuilder->codeAppendf(\"\\nhalf4 childOut1 = %s;\", _sample128.c_str());",
            "SkString _input1(\"childOut1\");",
            "SkString _sample174(\"_sample174\");",
            "this->invokeChild(_outer.child2_index, _input1.c_str(), &_sample174, args);",
            "this->registerChildProcessor(src.childProcessor(child1_index).clone());",
            "this->registerChildProcessor(src.childProcessor(child2_index).clone());"
         });
}

DEF_TEST(SkSLFPChildProcessorWithInputExpression, r) {
    test(r,
         "in fragmentProcessor child;"
         "void main() {"
         "    sk_OutColor = sample(child, sk_InColor * half4(0.5));"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {
            "this->registerChildProcessor(std::move(child));",
         },
         {
            "SkString _input0 = SkStringPrintf(\"%s * half4(0.5)\", args.fInputColor);",
            "SkString _sample64(\"_sample64\");",
            "this->invokeChild(_outer.child_index, _input0.c_str(), &_sample64, args);",
            "fragBuilder->codeAppendf(\"%s = %s;\\n\", args.fOutputColor, _sample64.c_str());",
            "this->registerChildProcessor(src.childProcessor(child_index).clone());",
         });
}

DEF_TEST(SkSLFPNestedChildProcessors, r) {
    test(r,
         "in fragmentProcessor child1;"
         "in fragmentProcessor child2;"
         "void main() {"
         "    sk_OutColor = sample(child2, sk_InColor * sample(child1, sk_InColor * half4(0.5)));"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {
            "this->registerChildProcessor(std::move(child1));",
            "this->registerChildProcessor(std::move(child2));"
         },
         {
            "SkString _input0 = SkStringPrintf(\"%s * half4(0.5)\", args.fInputColor);",
            "SkString _sample121(\"_sample121\");",
            "this->invokeChild(_outer.child1_index, _input0.c_str(), &_sample121, args);",
            "SkString _input1 = SkStringPrintf(\"%s * %s\", args.fInputColor, _sample121.c_str());",
            "SkString _sample93(\"_sample93\");",
            "this->invokeChild(_outer.child2_index, _input1.c_str(), &_sample93, args);",
            "fragBuilder->codeAppendf(\"%s = %s;\\n\", args.fOutputColor, _sample93.c_str());",
            "this->registerChildProcessor(src.childProcessor(child1_index).clone());",
            "this->registerChildProcessor(src.childProcessor(child2_index).clone());"
         });
}

DEF_TEST(SkSLFPChildFPAndGlobal, r) {
    test(r,
         "in fragmentProcessor child;"
         "bool hasCap = sk_Caps.externalTextureSupport;"
         "void main() {"
         "    if (hasCap) {"
         "        sk_OutColor = sample(child, sk_InColor);"
         "    } else {"
         "        sk_OutColor = half4(1);"
         "    }"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {
            "this->registerChildProcessor(std::move(child));"
         },
         {
            "hasCap = sk_Caps.externalTextureSupport;",
            "fragBuilder->codeAppendf(\"bool hasCap = %s;\\nif (hasCap) {\", (hasCap ? \"true\" : "
                                     "\"false\"));",
            "SkString _input0 = SkStringPrintf(\"%s\", args.fInputColor);",
            "SkString _sample130(\"_sample130\");",
            "this->invokeChild(_outer.child_index, _input0.c_str(), &_sample130, args);",
            "fragBuilder->codeAppendf(\"\\n    %s = %s;\\n} else {\\n    %s = half4(1.0);\\n}\\n\","
                                     " args.fOutputColor, _sample130.c_str(), args.fOutputColor);",

            "this->registerChildProcessor(src.childProcessor(child_index).clone());"
         });
}

DEF_TEST(SkSLFPChildProcessorInlineFieldAccess, r) {
    test(r,
         "in fragmentProcessor child;"
         "void main() {"
         "    if (child.preservesOpaqueInput) {"
         "        sk_OutColor = sample(child, sk_InColor);"
         "    } else {"
         "        sk_OutColor = half4(1);"
         "    }"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {
            "this->registerChildProcessor(std::move(child));"
         },
         {
            "fragBuilder->codeAppendf(\"if (%s) {\", "
                    "(_outer.childProcessor(_outer.child_index).preservesOpaqueInput() ? ",
            "SkString _input0 = SkStringPrintf(\"%s\", args.fInputColor);",
            "SkString _sample105(\"_sample105\");",
            "this->invokeChild(_outer.child_index, _input0.c_str(), &_sample105, args);",
            "fragBuilder->codeAppendf(\"\\n    %s = %s;\\n} else {\\n    %s = half4(1.0);\\n}\\n\","
                                     " args.fOutputColor, _sample105.c_str(), args.fOutputColor);",
            "this->registerChildProcessor(src.childProcessor(child_index).clone());"
         });
}

DEF_TEST(SkSLFPChildProcessorFieldAccess, r) {
    test(r,
         "in fragmentProcessor child;"
         "bool opaque = child.preservesOpaqueInput;"
         "void main() {"
         "    if (opaque) {"
         "        sk_OutColor = sample(child);"
         "    } else {"
         "        sk_OutColor = half4(0.5);"
         "    }"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {
            "this->registerChildProcessor(std::move(child));"
         },
         {
            "opaque = _outer.childProcessor(_outer.child_index).preservesOpaqueInput();",
            "fragBuilder->codeAppendf(\"bool opaque = %s;\\nif (opaque) {\", (opaque ? \"true\" : "
                                     "\"false\"));",
            "SkString _sample126(\"_sample126\");",
            "this->invokeChild(_outer.child_index, &_sample126, args);",
            "fragBuilder->codeAppendf(\"\\n    %s = %s;\\n} else {\\n    %s = half4(0.5);\\n}\\n\","
                                     " args.fOutputColor, _sample126.c_str(), args.fOutputColor);",
            "this->registerChildProcessor(src.childProcessor(child_index).clone());"
         });
}

DEF_TEST(SkSLFPNullableChildProcessor, r) {
    test(r,
         "in fragmentProcessor? child;"
         "void main() {"
         "    if (child != null) {"
         "        sk_OutColor = sample(child);"
         "    } else {"
         "        sk_OutColor = half4(0.5);"
         "    }"
         "}",
         *SkSL::ShaderCapsFactory::Default(),
         {},
         {
            "fragBuilder->codeAppendf(\"if (%s) {\", _outer.child_index >= 0 ? \"true\" : "
                                     "\"false\");",
            "SkString _sample93(\"_sample93\");",
            "if (_outer.child_index >= 0) {",
            "this->invokeChild(_outer.child_index, &_sample93, args);",
            "}",
            "fragBuilder->codeAppendf(\"\\n    %s = %s;\\n} else {\\n    %s = half4(0.5);\\n}\\n\","
                                     " args.fOutputColor, _sample93.c_str(), args.fOutputColor);"

         });
}

DEF_TEST(SkSLFPBadIn, r) {
    test_failure(r,
         "in half4 c;"
         "void main() {"
         "    sk_OutColor = c;"
         "}",
         "error: 1: 'in' variable must be either 'uniform' or 'layout(key)', or there must be a "
         "custom @setData function\n1 error\n");
}
